#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define SCULL_NR	8
#define DATA_LEN	1024

dev_t scull_dev;
struct class *scull_class;
struct scull {
	int scull_id;
	struct cdev scull_cdev;
	struct device *scull_device;
	void *dat;
	ssize_t dat_len;
} scull[SCULL_NR];

static int scull_open(struct inode *inode, struct file *filp)
{
	struct scull *tmp_scull;

	tmp_scull = container_of(inode->i_cdev, struct scull, scull_cdev);
	filp->private_data = (void *)tmp_scull;

	return 0;
}

static int scull_release(struct inode *inode, struct file *filp)
{
	return 0;
}

ssize_t scull_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
	int count;
	struct scull *tmp;

	tmp = (struct scull *)filp->private_data;

	count = tmp->dat_len < size ? tmp->dat_len : size;
	tmp->dat_len = copy_to_user(buf, tmp->dat, count);
	count -= tmp->dat_len;
	*offset += tmp->dat_len;

	return count;
}

ssize_t scull_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
	int count;
	int tmp_count;
	struct scull *tmp;

	tmp = (struct scull *)filp->private_data;

	if (DATA_LEN - tmp->dat_len == 0) {
		return -ENOMEM;
	}

	tmp_count = size < DATA_LEN - tmp->dat_len ? size : DATA_LEN - tmp->dat_len;
	count = copy_from_user(tmp->dat + tmp->dat_len, buf, tmp_count);
	count = tmp_count - count;
	tmp->dat_len += count;
	*offset += count;

	return count;
}

loff_t scull_llseek(struct file *filp, loff_t offset, int whence)
{
	return filp->f_pos;
}

static struct file_operations scull_ops = {
	.owner		= THIS_MODULE,
	.open		= scull_open,
	.release	= scull_release,
	.read		= scull_read,
	.write		= scull_write,
	.llseek		= scull_llseek,
};

int scull_init(void)
{
	int ret;
	int i;

	ret = alloc_chrdev_region(&scull_dev, 0, SCULL_NR, "scull_dev");
	if (ret < 0) {
		printk("scull is register_chrdev_region error.\n");
		goto err_1;
	}

	scull_class = class_create(THIS_MODULE, "sucll_class");
	if (IS_ERR(scull_class)) {
		printk("scull class_create error.\n");
		goto err_2;
	}

	for (i = 0; i < SCULL_NR; i++) {
		scull[i].scull_id = i;
		device_create(scull_class, NULL, scull_dev + i, NULL, "scull%d", i);
		cdev_init(&scull[i].scull_cdev, &scull_ops);
		scull[i].scull_cdev.owner = THIS_MODULE;
		cdev_add(&scull[i].scull_cdev, scull_dev + i, 1);

		scull[i].dat = kzalloc(DATA_LEN, GFP_KERNEL);
		if (scull[i].dat == NULL) {
			printk("Alloc dev mem error.\n");
		}
		scull[i].dat_len = 0;
	}
	printk("Scull installed\n");

	return 0;

err_2:
	unregister_chrdev_region(scull_dev, SCULL_NR);
err_1:
	return ret;
}

void scull_exit(void)
{
	int i;

	for (i = 0; i < SCULL_NR; i++) {
		kfree(scull[i].dat);
		cdev_del(&scull[i].scull_cdev);
		device_destroy(scull_class, scull_dev + i);
	}

	class_destroy(scull_class);
	unregister_chrdev_region(scull_dev, SCULL_NR);
	printk("Exited scull.\n");
}

module_init(scull_init);
module_exit(scull_exit);
MODULE_AUTHOR("YYW");
MODULE_LICENSE("GPL");
